#!/usr/bin/env python
import os
import re
import string
import sys
import uuid


class AttrDictSimple(dict):
    def __getattr__(self, attr):
        return self[attr]

    def __setattr__(self, attr, value):
        self[attr] = value

    def __delattr__(self, attr):
        del self[attr]


try:
    input = raw_input
except NameError:
    pass


class Template(string.Template):
    delimiter = "@"

    def __init__(self, filename):
        if not os.path.isfile(filename):
            raise ValueError('Unable to read file with name {0}'
                             ''.format(filename))
        super(self.__class__, self).__init__(open(filename).read())

    def process(self, *args):
        params = AttrDictSimple()
        for arg in args:
            params.update(self.generateReplacementDict(arg))
        return self.substitute(params)

    def generateReplacementDict(self, obj):
        if isinstance(obj, dict):
            return obj
        assert isinstance(obj, Base), "Must provide a base FBGen class"
        retdict = AttrDictSimple([("{0}_{1}".format(obj.__class__.__name__.
                                                    upper(), k), obj[k]) for k
                                  in obj.keys.keys() if hasattr(obj, k)])
        if retdict.get('PLUGIN_disable_gui') is not None:
            if retdict.get('PLUGIN_disable_gui') == "true":
                retdict.update(PLUGIN_disable_gui='set (FB_GUI_DISABLED')
                retdict.update(PLUGIN_disable_gui_mac='0')
            else:
                retdict.update(PLUGIN_disable_gui='#set (FB_GUI_DISABLED')
                retdict.update(PLUGIN_disable_gui_mac='1')
        return retdict

SENTENCE_SYMBOLS_RE = re.compile(r"^[()\w .:,;!?-]+$")
SENTENCE_SYMBOLS_WARNING = 'must be at least one character long and may only' \
                           ' and may only contain letters, digits, spaces, ' \
                           'punctuation marks, parantheses, underscores or ' \
                           'hyphens.'


class Base(object):
    def __getitem__(self, item):
        return getattr(self, item)

    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            if hasattr(self, k):
                setattr(self, k, v)
        self.keys = AttrDictSimple(
            name=("Name", SENTENCE_SYMBOLS_RE, "Name " +
                  SENTENCE_SYMBOLS_WARNING),
            ident=("Identifier", re.compile(r"^[a-zA-Z][a-zA-Z\d_]{2,}$"),
                   "Identifier must be 3 or more alphanumeric characters"
                   " (underscore allowed)."),
            desc=("Description", SENTENCE_SYMBOLS_RE, "Description " +
                  SENTENCE_SYMBOLS_WARNING),
            prefix=("Prefix", re.compile(r"^[a-zA-Z][a-zA-Z\d_]{2,4}$"),
                    "Prefix must be 3 to 5 alphanumeric characters"
                    " (underscores allowed)."),
            domain=("Domain",
                    re.compile(r"^([a-zA-Z0-9]+(\-[a-zA-Z0-9]+)*\.)*"
                               "[a-zA-Z0-9]+(\-[a-zA-Z0-9]+)*"
                               "\.[a-zA-Z]{2,4}$"),
                    "Domain must be a valid domain name."),
            mimetype=("MIME type", re.compile(r"^[a-zA-Z0-9]+"
                                              "\/[a-zA-Z0-9\-]+$"),
                      "Please use alphanumeric characters and dashes in the"
                      " format: application/x-firebreath"),
            disable_gui=("has no UI", re.compile(r"^true$|false$"),
                         "Please enter valid input: true or false"),
        )

    def getValue(self, key, default):
        desc, regex, error = self.keys[key]
        if default is None:
            default = ""
        value = input("{0} {1} [{2}]: ".format(self.__class__.__name__, desc,
                                               default)) or default
        if regex.match(value):
            return value
        else:
            print("Invalid syntax: {0}".format(error))
            return self.getValue(key, default)

    def promptValues(self):
        """
        Override in sub-classes. Prompts for necessary configuration
        information.
        """
        pass

    def readCfg(self, cfg):
        """
        Override in sub-classes. Reads an existing configuration out of a file
        for anything not already defined by other means.
        """
        pass

    def updateCfg(self, cfg):
        """
        Override in sub-classes. Updates a configuration object with current
        values.
        """
        pass


class JSAPI_Member(Base):
    """
    Used to define a JSAPI Member. This may go away in a future version as I
    haven't quite decided how to deal with these yet.
    """
    ident = None
    type = None

    def __init__(self):
        print("Initializing JSAPI_Member")
        self.types = AttrDictSimple(
            string="std::string",
            int="long",   # changed int to long since IE will pass it as
                          # a long anyway and we want to avoid issues.
            long="long",
            double="double",
            bool="bool",
            variant="FB::variant",
            dynamic="FB::VariantList",
            JSOBJ="FB::JSAPIPtr",
            API="FB::JSObject",
        )

    def translateType(self, type):
        return self.types[type]

    def isValidType(self, type):
        return type in self.types

    def setType(self, type):
        self.type = type

    def getRealType(self):
        return self.translateType(self.type)


class JSAPI_Property(JSAPI_Member):
    """
    Used to define a JSAPI Property. This may go away in a future version as
    I haven't quite decided how to deal with these yet.
    """
    def __init__(self, ident, type):
        super(JSAPI_Property, self).__init__()
        if not self.isValidType(type):
            raise Exception('Invalid type {0}.  Valid types are: {1}'
                            ''.format(type, ', '.join(self.types)))
        self.type = type
        self.ident = ident


class JSAPI_Method(JSAPI_Member):
    """
    Used to define a JSAPI Method. This may go away in a future version as I
    haven't quite decided how to deal with these yet.
    """
    argTypes = ["string"]

    def __init__(self, ident, type, argTypes):
        super(JSAPI_Method, self).__init__()
        self.type = type
        self.ident = ident
        self.argTypes = argTypes
        for curArg in argTypes:
            if not self.isValidType(curArg):
                raise Exception('Invalid type {0}.  Valid types are: {1}'
                                ''.format(curArg, ', '.join(self.types)))

    def getRealArgTypes(self):
        retVal = []
        for cur in self.argTypes:
            retVal.append(self.translateType(cur))
        return retVal


class Plugin(Base):
    name = None
    ident = None
    prefix = None
    desc = None
    mimetype = None
    disable_gui = None

    def makeDefaultPrefix(self, startName, delim=" "):
        MIN_LEN_PREFIX = 3
        MAX_LEN_PREFIX = 5
        pattern = re.compile(r"([A-Z][A-Z][a-z])|([a-z][A-Z])")
        if startName is None:
            return None
        if MIN_LEN_PREFIX <= len(startName) <= MAX_LEN_PREFIX:
            return startName.upper()
        normalize = lambda s: s
        seperated = normalize(pattern.sub(lambda m: m.group()[:1] + delim +
                                          m.group()[1:], startName))
        words = seperated.split()
        if MIN_LEN_PREFIX <= len(words) <= MAX_LEN_PREFIX:
            return "".join([lett[0] for lett in
                            words]).upper()[:MAX_LEN_PREFIX]
        postfix = ""
        word = len(words) - 1
        needed = MIN_LEN_PREFIX - len(words) + 1
        while len(postfix) < needed:
            stillNeeded = needed - len(postfix)
            postfix = words[word][:stillNeeded] + postfix
            if len(postfix) < needed:
                needed += 1
                word -= 1
        return "".join([lett[0] for lett in
                        words[:word]]).upper() + postfix.upper()

    def __init__(self, **kwargs):
        super(Plugin, self).__init__(**kwargs)
        if self.mimetype:
            self.mimetype = self.mimetype.lower()

    def promptValues(self):
        name = self.name
        ident = self.ident

        self.name = self.getValue("name", self.name)
        self.ident = self.getValue("ident", re.sub(r"[^a-zA-Z\d\-_]", "",
                                                   self.ident or self.name))
        self.prefix = self.getValue("prefix", self.prefix
                                    if name == self.name
                                    else self.makeDefaultPrefix(self.name))
        self.mimetype = self.getValue("mimetype", self.mimetype
                                      if ident == self.ident
                                      else 'application/x-{0}'
                                      ''.format(self.ident.lower())).lower()
        self.desc = self.getValue("desc", self.desc)
        self.disable_gui = self.getValue("disable_gui", self.disable_gui or
                                         "false").lower()

    def readCfg(self, cfg):
        if not cfg.has_section("plugin"):
            return
        self.name = self.name or cfg.get("plugin", "name")
        self.ident = self.ident or cfg.get("plugin", "ident")
        self.prefix = self.prefix or cfg.get("plugin", "prefix")
        self.mimetype = self.mimetype or cfg.get("plugin", "mimetype").lower()
        self.desc = self.desc or cfg.get("plugin", "description")

        if self.disable_gui is None:
            self.disable_gui = cfg.get("plugin", "disable_gui") or False

    def updateCfg(self, cfg):
        if not cfg.has_section("plugin"):
            cfg.add_section("plugin")
        cfg.set("plugin", "name", self.name)
        cfg.set("plugin", "ident", self.ident)
        cfg.set("plugin", "prefix", self.prefix)
        cfg.set("plugin", "mimetype", self.mimetype)
        cfg.set("plugin", "description", self.desc)
        cfg.set("plugin", "disable_gui", self.disable_gui)

    def __str__(self):
        return '\nPlugin Details:\n--------------\nName:        {0}' \
               '\nIdentifier:  {1}\nPrefix:      {2}\nMIME type:   {3}' \
               '\nDescription: {4}\nGUI:  {5}' \
               ''.format(self.name, self.ident, self.prefix, self.mimetype,
                         self.description, self.disable_gui)


class Company(Base):
    name = None
    ident = None
    domain = None

    def __init__(self, **kwargs):
        super(Company, self).__init__(**kwargs)

    def promptValues(self):
        self.name = self.getValue("name", self.name)
        self.ident = self.getValue("ident", self.ident or
                                   re.sub(r"[^a-zA-Z\d\-_]", "", self.name))
        self.domain = self.getValue("domain", self.domain or
                                    "{0}.com".format(self.ident.lower()))

    def readCfg(self, cfg):
        if not cfg.has_section("company"):
            return
        self.name = self.name or cfg.get("company", "name")
        self.ident = self.ident or cfg.get("company", "ident")
        self.domain = self.domain or cfg.get("company", "domain")

    def updateCfg(self, cfg):
        if not cfg.has_section("company"):
            cfg.add_section("company")
        cfg.set("company", "name", self.name)
        cfg.set("company", "ident", self.ident)
        cfg.set("company", "domain", self.domain)

    def __str__(self):
        return '\nCompany Details\n---------------\nName:        {0}\n' \
               'Identifier:  {1}\nDomain:      {2}' \
               ''.format(self.name, self.ident, self.domain)


class GUID(Base):
    """
    This class will create a Master GUID based on the plugin identifier and
    company domain. This allows the generated GUIDs to always be the same if
    created with/for the same intent.
    """
    master = None
    domain = None
    ident = None

    def __init__(self, **kwargs):
        super(GUID, self).__init__(**kwargs)
        self.master = uuid.uuid5(uuid.NAMESPACE_DNS, self.ident + '.' +
                                 self.domain)

    def generate(self, string):
        return uuid.uuid5(self.master, string)
